其他
比较 Rust 和 Java
从我的其它博客可能很难看得出来我是一个 Java 开发者。坦率地说我很喜欢 Java,它甚至可能让我成为某些少数派的一员。
然而,如果你看到过我发表的其它一些文章,你就会发现我也非常喜欢使用 Rust 编程。所以,我懂 Rust 也懂Java,既然如此,为何不对二者进行一个比较,看看会得到什么样的结论呢?
在早期的互联网时代,Java 被宣传为 “Web 的编程语言”,这都是因为 applet 。但这并没有确定的工作和计划,尽管现今的语言被运行在 web 客户端是开始于 ‘Java’。但是,现今的大多数 Web 架构都被运行在 Java 服务器侧的。
Rust 1.0 长期处于开发(大部分历史可以在 GitHub 上看到,那可以回顾到 2010 年)之中,在 2015 年 5 月被发布。Graydon Hoare 开始这个项目实际上可以追溯到 2008 年左右。自从 2010 年开始, Mozilla 就赞助其开发,随之建立 Servo 项目,旨在创建一个在 Rust 中的现代浏览器引擎。
Rust 开发者开始在设计上进行许多迭代,很多东西被不断扔掉。早期的 Rust 版本就有绿色的线程,如果没记错的话,垃圾收集已经在 0.8 版本期间被移除。而快速迭代曾令很多早期用户不快,但拥有一起公开讨论的文化,使它拥有一个非常全面和周到的设计。
作为一个完全编译的语言,Rust(理论上)不会像JAVA语言一样有着灵活的跨平台性,但Rust基于LLVM,这使得很多(使用Rust的)后端,可以将开销控制在一定范围。此外,由于缺乏一个庞大的运行时环境及其垃圾回收器使得Rust相较JVM而言更加轻量。
这是JAVA所没有的特性,同时为Rust引入了一系列的利与弊。前者因为数据竞争、ConcurrentModificationException及其它祸害JAVA代码相关的东西,编译器会确保其自由性;而后者因为在一些情况下,每个人学习Rust的时候都会运行其head脚本并不用borrow checker,然后问Rust之神为什么他们完美手动添加的生命周期的注解不正确。
事实上,规则很简单:你可以拥有一个素数(你可以做任何事,当借进来时期望丢弃它),一个可变的亦或像许多一成不变的暂借,只要你喜欢。借以相反的顺序结束,这意味着有时候切换开关语句可以借以检查程序。
我喜欢添加那些尽管看起来很巨大的差异,Java的存储模型和Rust是惊人的相似。
Java的整型操作均是封装好的(并没有溢出检查),而Rust的在调试模式进行溢出检查,在发布模式下进行封装不做检查。这使得Rust在测试期间能够检查溢出发现问题,并在发布版本不进行检查提高执行效率。
Rust具有内置的元组类型,可以很容易地基本无额外开销的返回多个值。在Java中,返回一个<A,B>对总是有点麻烦(并附带引用追溯的开销)。在Java中,有人建议增加值类型,从而使其同其它低级语言保持一致,但听说这个建议至少推到Java的10版本或者更后的版本中去考虑。
总而言之,更加彻底的类型系统、借鉴和其它的检查,以及默认的不可修改特性,还有大量蹩脚特性的缺失,这些都意味着在大致相同的时间内写出来的 Rust 代码要不 Java 代码更加的稳定。换言之: 编写 Rust 代码可能要比 Java 更难,但同时写出不正确的 Rust 代码也要比写出不正确的 Java 代码难很多。
我可能不需要重申,Java类里将数据和行为绑定在一起(封装),,控制访问, 继承于其他类, 实现接口等等。Java里的所有东西(除了一些基础数据类型,基本上是空)都是对象 (并且都是某一个类),哪怕它只是一堆静态方法的集合。类相当于类型。大量的基础类构成了Java结构的中心。
相类似,Rust有traits,和Java8的接口诡异的相似。它也有类型(通常是结构体或者枚举)和类型接口实现。它也有固定实现(他们自身类型)。最后访问域通常是模块(模块类似于Java包,但是Java包只是一些类和子包的集合)决定。
这种数据和行为分离的方式在开始会显得有点奇怪,但是实际上它很巧妙, 因为它是的数据类型的复合变得非常自然,并且也可以在已经存在的类型上增加新的traits,这在java里完全不可能。
Rust也有只存活在模块内部的独立函数,这表示在写程序代码的时候可以少些很多套话。比如不用类似public class HelloWorld。
Rust的数据和操作分离的方式候提供了一种基于数据的编程方法,你可以首先创建你的数据结构,然后再围绕它进行构造操作。
许多Java开发者拥护SOLID原则,这使得在Java中依旧需要一些设计模式再次重申SOLID原则,单一责任原则,开放封闭原则,里氏替换原则 ,接口分离原则和依赖倒置。
有时,SOLID原则会使应用和类库过度工程化。就像齿轮一样运行,即通过反复调用多层级的对象并且每个对象只负责完成一部分功能特别在企业级应用的代码中,SOLID原则被广泛应用。幸运地是坚持SOLID原则有一个好处是,利于代码重用。
在Rust中,trait一致性(特别是orphan原则)在设计上基本与开放封闭原则相匹配。接口分离原则和依赖倒置能够通过traits实现,但是通常需要泛型,这将造成代码更复杂或生成更多的trait对象,进而增加运行时间。这造成在Rust开发人员不愿意付出上诉代价来坚持接口分离原则和依赖倒置原则。单一责任原则和里氏替换原则在两种语言中都依赖于开发者的觉悟。
注意这个列表并不完全,及一些并没有完全与所有案例进行比较。大多数Rust的构造器对于不同loop和match声明的不同混合实际上是语法糖(syntactic sugar)。在JAVA中,for-each循环编译为基于迭代的for循环,其结构为for (Iterator<_> i = _; i.hasNext();) { _ v = i.next(); _ }。
所以简而言之,Rust去掉了C语言那样的for循环。因为分配语句没有返回值(a = b = c这样的语句在Rust中不合法),所以if let 和 while let 之类形式的语句要关心这些,这使得这样的语句看起来更加直观, if (x = y) 这样的错误也不大会出现(不过公平的说,大多数JAVA的IDE也会捕捉这些错误)。
Rust的语法糖围绕着异常强大的解构化的match,使得Rust在某些方面比JAVA更高级,但编译器依然设法生成基于语法糖的更紧凑代码,这些代码大多数要感谢LLVM, Rust编译器使用LLVM来生成代码。
Rust现在有与线程绑定的“panics”可以被视为运行时异常,它会杀死该线程并应该只能够被另外一个线程捕获.在最近的Rust版本中,使用下面类似:"std::panic::recover(_) that can call a closure and return a"的语句能够将结果中的任何“panic”转换成"Error"。但是这个功能还不稳定,只能够在每日更新的Rust开发版本中使用。
Rust的函数隐式实现了一些 Fn*() -> _ 类型,因此可以在没有分配栈的情况下,进行不同设置。调用者必须用所给定类型绑定才能正常运行,这通常需要需要一些技巧。然而,某个调用者可以调用比JAVA更加有规则的策略。
JAVA的流提供了低消耗的方式数据并行计算。Rust并没有直接提供支持,但 Rayon 提供了相较低消耗的并行迭代器,然而还有许多其它第三方的库瞄准了并行与并发方面。
JAVA提供可变函数,以内部使用的数组。然而有些小技巧,在某些情况下可以考虑更好的接口。Rust至少可通过宏模拟,或使用切片参数来模拟可变函数,但或许某天Rust也会提供。
JAVA分配函数基于参数类型,内部修改函数名以包括签名,类似如 next()Lllogiq.example.Example 。而Rust并不这样实现:函数永远总是取一系列的参数,尽管泛型可以扩宽可能的签名中的类型集,如 some_func<S: Into<String>>(s: S) 。
正相反,Java 有少许类库能够在运行时创建字节码,这些类库能够将改变反馈给类加载器。其中一些类库非常好用(Byte Buddy 的作者 Rafael Winterhalter 大声疾呼)考虑到这点,Java 能够在运行时合成代码,令人惊讶的是这种事情不常发生。其次,大量的代码运行良好没有任何字节码的注入。
作为注释,目前阶段 Java 对于程序元数据的支持优于 Rust。这种情况与工具紧密相连,未来我们将完善这部分。时间会告诉我们 Rust 能否在这方面追赶上 Java。
Java 也有运行时反射,该特性又笨重又慢;编译好的字节码总是更快。 在 Rust 中你只需要关注你所需要的,因此你可以使用宏实现任何反射能力。这需要做更多的工作,但是你能掌控任何事情.
Rust 明显的好处是层级低,与 C 的接口只需更少的操作。尽管 Java 的设计者真的担心人们会本地化太频繁,但他们的担心是毫无意义的,因为 Java 本身就做得很好。
Java 不抛弃不放弃。因此,它的 API 已经有三个 UI 工具集(AWT,SWing 和 JavaFX),有Enumeration(枚举) 和 Iterator (迭代器) 接口(它们做的几乎是同样的事情),还有两个 IO 类(java.io 和 java.nio,尽管我承认后者以前者为基础)集合和其他有趣的东西。
Java 的官方 API 倾向于零意外,并且需要极其完整的文档。 由于 Steve Klabnik 被 Mozilla 雇佣为 Rust 的文档专家,也因为他的工作,Rust 的文档也能紧随其后。
Rust 的库是精益和敏锐的。它有一些集合类,包括大量的字符串处理,智能引用和 cell,并支持基本的并发性,还有很多 IO/网络和迷你的 OS 集成。正是因为这样, Rust 的代码将会依赖一些第三方库,不过这些库非常容易获得和管理。这里好的一面是,如果他们只是想要写一个 JSON 解析器,那么就不需要下载一组的 MIDI 类的 - 或者反之亦然。
Rust 的 API 文档允许离线关键词搜索, 这个在你知道自己要寻找是什么的时候很不错。许多的类型通过许多的特性来协调它们自己的行为,这或多或少的阻碍了其可探索性。换句话来讲,一旦你了解了你所要使用的特性,你就能用它们做出令人惊奇的东西来。
尽管同 Java API 的规模相比有点相形见绌,但 Rust 标准库已经具备了令人惊讶的能力。一部分API被标记为不稳定的, 这意味它只会在一些实验性质的编译器或者某些 #![feature(_)] 注解上有用。这样可以使库的开发团队在快速迭代API设计的同时保证版本的稳定。另外,这也限制诸如 BTreeMap 这样的无用特性, 如此会有相当多的方法在发行版本的 Rust 中被去掉。这种情况非常有可能随着时间的推移得到改善。
Rust 大多数时候还不会产生分歧(尽管一些封装明显的相同),因为 Rust 的工具还没有像 Java 一样成熟。但是,Cargo 已经能够对构建和打包进行完善的管理--我希望在 Java 中也能够使用它。在实际中,我至少能完成编码,但是Rust核心团队将今年定义为IDE年.我们相信我们将看到它成功.
Rust的社区与Java相比还很小,但是这里有很多熟练使用 Rust 的人,他们友善,乐于助人,而且幽默,在这里的每一次交流都使我感到快乐, 一点都不烦恼。一些人说他们被迫坚守编码行为准则,但是我还没有看到任何人反对特定的代码,我相信最后的结果会为他们发声的.
在这次比较中 Rust 不被看好,但是就目前来说 Rust 已经做到很好啦。尽管 Rust 的社区规模小,但是 Rust 社区通过敏捷,智慧和专注弥补了人员的不足。在社区成员的支持与帮助下,Rust 成为了一个伟大的语言。
本文为开源中国社区翻译频道翻译文章,由于篇幅较长,本文摘抄其中精简内容,阅读全文请点击“阅读原文”
欢迎任何形式的转载,但请务必注明出处,尊重他人劳动成果。转载时请注明:文章转载自开源中国公众号 (ID:oschina2013)
了解更多详情请点击“阅读原文”
开源中国|ID:oschina2013
每天为你送上精选资讯早点
还有每天的 OSChina 乱弹哦